package org.wdm.parsers;

import java.util.*;
import org.wdm.beans.Bus;
import org.wdm.beans.BusStop;
import org.wdm.beans.Route;
import org.wdm.beans.Step;
import org.wdm.beans.points.BusStopPoint;
import org.wdm.beans.points.Point;
import org.wdm.utils.BusUtils;
import org.wdm.utils.Utils;

/*
 * To change this template, choose Tools | Templates and open the template in
 * the editor.
 */
/**
 *
 * @author thanasis
 */
public class RouteCalculation {

    public static List<Route> calculateRoute(HashMap<String, BusStop> busStopMap, HashMap<String, Bus> busMap, Point start, Point end, double maxWalkingDistance) {
        List<Route> routesList = new ArrayList<Route>();
        TreeMap<Double, BusStop> closerToStartBusStops = BusUtils.getCloserBusStop(start, busStopMap);
        TreeMap<Double, BusStop> closerToEndBusStops = BusUtils.getCloserBusStop(end, busStopMap);
        for (Map.Entry<Double, BusStop> startEntry : closerToStartBusStops.entrySet()) {
            Double startKey = startEntry.getKey();
            BusStop startValue = startEntry.getValue();
            if (startKey > maxWalkingDistance) {
                break;
            }

            for (Map.Entry<Double, BusStop> endEntry : closerToEndBusStops.entrySet()) {
                Double endKey = endEntry.getKey();
                BusStop endValue = endEntry.getValue();
                if (endKey > maxWalkingDistance) {
                    break;
                }
                BusStop closerToStartBusStop = startValue;
                BusStop closerToEndBusStop = endValue;


                Route directReachingBusStop = directBusRoute(start, closerToStartBusStop, startKey, end, closerToEndBusStop, endKey, "M");

                if (directReachingBusStop != null) {
                    routesList.add(directReachingBusStop);
                } else {
//                    Route undirectedBusRoutes = undirectReachBusStop(start, closerToStartBusStop, startKey, end, closerToEndBusStop, endKey, "M");
//                    if (undirectedBusRoutes != null) {
//                        routesList.add(undirectedBusRoutes);
//                    }
                }
            }
        }
        return routesList;
    }

    private static Route directBusRoute(BusStop startBusStop, BusStop endBusStop, String unit) {
        return directBusRoute(startBusStop.getPoint(), startBusStop, 0, endBusStop.getPoint(), endBusStop, 0, unit);
    }

    private static Route directBusRoute(Point start, BusStop startBusStop, double fromStartDistance, Point end, BusStop endBusStop, double toEndDistance, String unit) {
        Bus reachingBus = null;
        double reachingBusRouteDistance = Double.MAX_VALUE;
        int direction = 0;
        int startIndex = -1;
        int endIndex = -1;
        List<Bus> busesList = startBusStop.getBusesList();
        for (Bus bus : busesList) {
            if (bus.containsBusStop(startBusStop) == 1 && bus.containsBusStop(endBusStop) == 1) {
                if (!BusUtils.isStartBeforeEnd(bus.getGoing(), startBusStop, endBusStop)) {
                    continue;
                }
                double goingBusRouteDistance = BusUtils.getRouteDistance(bus.getGoing(), startBusStop, endBusStop, unit);
                if (goingBusRouteDistance < reachingBusRouteDistance) {
                    reachingBusRouteDistance = goingBusRouteDistance;
                    direction = 1;
                    reachingBus = bus;
                    startIndex = bus.getGoing().indexOf(startBusStop);
                    endIndex = bus.getGoing().indexOf(endBusStop);
                }
            }
            if (bus.containsBusStop(startBusStop) == -1 && bus.containsBusStop(endBusStop) == -1) {
                if (!BusUtils.isStartBeforeEnd(bus.getReturning(), startBusStop, endBusStop)) {
                    continue;
                }
                double returningBusRouteDistance = BusUtils.getRouteDistance(bus.getReturning(), startBusStop, endBusStop, unit);
                if (returningBusRouteDistance < reachingBusRouteDistance) {
                    reachingBusRouteDistance = returningBusRouteDistance;
                    direction = -1;
                    reachingBus = bus;
                    startIndex = bus.getReturning().indexOf(startBusStop);
                    endIndex = bus.getReturning().indexOf(endBusStop);
                }
            }
        }
        if (reachingBus != null) {
            Route route = new Route();
            List<BusStopPoint> busStopsInRoute = new ArrayList<BusStopPoint>();
            if (direction == 1) {
                for (int i = startIndex; i <= endIndex; i++) {
                    BusStop busStop = reachingBus.getGoing().get(i);
                    BusStopPoint busStopPoint = new BusStopPoint(busStop.getNaptan(), busStop.getName(), reachingBus.getRoute(), busStop.getPoint().getLatitude(), busStop.getPoint().getLongitude());
                    busStopsInRoute.add(busStopPoint);
                }
            } else if (direction == -1) {
                for (int i = startIndex; i <= endIndex; i++) {
                    BusStop busStop = reachingBus.getReturning().get(i);
                    BusStopPoint busStopPoint = new BusStopPoint(busStop.getNaptan(), busStop.getName(), reachingBus.getRoute(), busStop.getPoint().getLatitude(), busStop.getPoint().getLongitude());
                    busStopsInRoute.add(busStopPoint);
                }
            }

            if (busStopsInRoute.get(0).getLatitude() != start.getLatitude() || busStopsInRoute.get(0).getLongitude() != start.getLongitude()) {
                Step startStep = new Step("WALKING", fromStartDistance, "miles", 0, "", start, busStopsInRoute.get(0));
                route.addStep(startStep);
            }

            for (int i = 0; i < busStopsInRoute.size() - 1; i++) {
                BusStopPoint startPoint = busStopsInRoute.get(i);
                BusStopPoint endPoint = busStopsInRoute.get(i + 1);
                Step step = new Step("TRANSPORT", Utils.distance(startPoint, endPoint, unit), "miles", 0, "", startPoint, endPoint);
                route.addStep(step);
            }

            if (busStopsInRoute.get(busStopsInRoute.size() - 1).getLatitude() != end.getLatitude() || busStopsInRoute.get(busStopsInRoute.size() - 1).getLongitude() != end.getLongitude()) {
                Step endStep = new Step("WALKING", toEndDistance, "miles", 0, "", busStopsInRoute.get(busStopsInRoute.size() - 1), end);
                route.addStep(endStep);
            }

            route.computeDistance();

            return route;
        } else {
            return null;
        }
    }

    public static Route undirectReachBusStop(Point start, BusStop startBusStop, double fromStartDistance, Point end, BusStop endBusStop, double toEndDistance, String unit) {
        Bus startToMiddleBus = null;
        double closerMiddleToEndDistance = Double.MAX_VALUE;
        BusStop closerMiddleBusStop = null;
        int startToMiddleDirection = 0;
        List<Bus> busesListFromStart = startBusStop.getBusesList();
        for (Bus startBus : busesListFromStart) {
            for (BusStop middleBusStop : startBus.getGoing()) {
                if (!BusUtils.isStartBeforeEnd(startBus.getGoing(), startBusStop, middleBusStop)) {
                    continue;
                }

                double middleToEndDistance = middleBusStop.getDistance(endBusStop.getPoint(), "M");
                if (middleToEndDistance < closerMiddleToEndDistance) {
                    Bus reachingBus = BusUtils.containsAtLeastOneBus(middleBusStop, endBusStop);
                    if (reachingBus != null) {
                        startToMiddleBus = startBus;
                        closerMiddleBusStop = middleBusStop;
                        startToMiddleDirection = 1;
                    }
                } else {
                    continue;
                }
            }

            for (BusStop middleBusStop : startBus.getReturning()) {
                if (!BusUtils.isStartBeforeEnd(startBus.getReturning(), startBusStop, middleBusStop)) {
                    continue;
                }

                double middleToEndDistance = middleBusStop.getDistance(endBusStop.getPoint(), "M");
                if (middleToEndDistance < closerMiddleToEndDistance) {
                    Bus reachingBus = BusUtils.containsAtLeastOneBus(middleBusStop, endBusStop);
                    if (reachingBus != null) {
                        startToMiddleBus = startBus;
                        closerMiddleBusStop = middleBusStop;
                        startToMiddleDirection = -1;
                    }
                } else {
                    continue;
                }
            }

            if (closerMiddleBusStop != null) {
                Route route = new Route();
                List<BusStopPoint> busStopsInRoute = new ArrayList<BusStopPoint>();
                if (startToMiddleDirection == 1) {
                    for (int i = startToMiddleBus.getGoing().indexOf(startBusStop); i <= startToMiddleBus.getGoing().indexOf(closerMiddleBusStop); i++) {
                        BusStop busStop = startToMiddleBus.getGoing().get(i);
                        BusStopPoint busStopPoint = new BusStopPoint(busStop.getNaptan(), busStop.getName(), startToMiddleBus.getRoute(), busStop.getPoint().getLatitude(), busStop.getPoint().getLongitude());
                        busStopsInRoute.add(busStopPoint);
                    }
                } else if (startToMiddleDirection == -1) {
                    for (int i = startToMiddleBus.getReturning().indexOf(startBusStop); i <= startToMiddleBus.getReturning().indexOf(closerMiddleBusStop); i++) {
                        BusStop busStop = startToMiddleBus.getReturning().get(i);
                        BusStopPoint busStopPoint = new BusStopPoint(busStop.getNaptan(), busStop.getName(), startToMiddleBus.getRoute(), busStop.getPoint().getLatitude(), busStop.getPoint().getLongitude());
                        busStopsInRoute.add(busStopPoint);
                    }
                }

                if (busStopsInRoute.get(0).getLatitude() != start.getLatitude() && busStopsInRoute.get(0).getLongitude() != start.getLongitude()) {
                    Step startStep = new Step("walking", fromStartDistance, "miles", 0, "", start, busStopsInRoute.get(0));
                    route.addStep(startStep);
                }

                for (int i = 0; i < busStopsInRoute.size() - 1; i++) {
                    BusStopPoint startPoint = busStopsInRoute.get(i);
                    BusStopPoint endPoint = busStopsInRoute.get(i + 1);
                    Step step = new Step("tranport", Utils.distance(startPoint, endPoint, unit), "miles", 0, "", startPoint, endPoint);
                    route.addStep(step);
                }

                Route directReachBusStop = directBusRoute(closerMiddleBusStop, endBusStop, "M");
                if (directReachBusStop == null) {
//                    System.out.println(closerMiddleBusStop);
//                    System.out.println("\n" + endBusStop);

                    return null;
                }
                route.addSteps(directReachBusStop.getSteps());

                if (busStopsInRoute.get(busStopsInRoute.size() - 1).getLatitude() != end.getLatitude() && busStopsInRoute.get(busStopsInRoute.size() - 1).getLongitude() != end.getLongitude()) {
                    Step endStep = new Step("walking", toEndDistance, "miles", 0, "", busStopsInRoute.get(busStopsInRoute.size() - 1), end);
                    route.addStep(endStep);
                }

                route.computeDistance();

                return route;
            }
        }
        return null;
    }
}
